package mk.jug.academy.service;

import java.util.List;

import mk.jug.academy.exception.BusinessException;
import mk.jug.academy.model.Student;
import mk.jug.academy.model.status.StudentStatus;
import mk.jug.academy.preconditions.ErrorCodes;
import mk.jug.academy.testing.bdd.IntegrationBehavior;
import mk.jug.academy.testing.factory.StudentTestFactory;

import org.springframework.beans.factory.annotation.Autowired;

import com.googlecode.instinct.marker.annotate.Specification;
import com.googlecode.instinct.marker.annotate.Specification.SpecificationState;
import com.googlecode.instinct.marker.annotate.Subject;

/**
 * An integration test regarding the student registration
 * process.
 * 
 * @author Ice.Penov
 */
public class StudentLifecycle extends IntegrationBehavior {
				
	@Autowired private StudentService studentService;	
	@Subject private Student student;
	
	@Override
	protected void beforeHook() {
		student = StudentTestFactory.createStudent();		
	}
	
	/**
	 * User action:
	 * A student tries to register without supplying the mandatory info.
	 * 
	 * Expected behavior:
	 * 1. The registration process should fail with {@link IllegalArgumentException}
	 * 2. The student should not be persistent in the database
	 */
	@Specification(state = SpecificationState.COMPLETE, expectedException = IllegalArgumentException.class)
	public void failToRegisterStudentWhenMandatoryInformationIsMissing() {
		student.setFirstName(null);
		studentService.registerStudent(student);
		List<Student> students = studentService.listAll(1, 2).getItems();
		expect.that(students).isEmpty();
	}
	
	/**
	 * User action:
	 * A {@link Student} with already registered email tries to register.
	 * 
	 * Expected behavior:
	 * 1. Throw an {@link BusinessException} with the appropriate error code.
	 */
	@Specification(state = SpecificationState.COMPLETE)
	public void failToRegisterStudentWithExistingEmail() {
		studentService.registerStudent(student);
		new ExceptionExpectation() {
			public void execute() {
				studentService.registerStudent(student);
			}
		}.checkForErrorCode(BusinessException.class, ErrorCodes.ERROR_EMAIL_EXISTS);		
	}
	
	/**
	 * User action:
	 * A student with complete registration info tries to register.
	 * 	
	 * Expected behavior:
	 * 1. The student should be persistent in the database.
	 * 2. The student should be in the {@link StudentStatus#SIGNED_UP}
	 */
	@Specification(state = SpecificationState.COMPLETE)
	public void proceedWithStudentRegistrationWhenInformationIsComplete() {
		studentService.registerStudent(student);
		List<Student> students = studentService.listAll(1, 2).getItems();
		Student expected = students.get(0);
		expect.that(expected.getId()).isEqualTo(student.getId());
		expect.that(student.getStatus()).isEqualTo(StudentStatus.SIGNED_UP);
		expect.that(studentService.listSignedUp(1, 2).getItems()).isOfSize(1);
	}
	
	@Specification(state = SpecificationState.COMPLETE)
	public void failToAcceptStudentIfNotInSignedUpState() {
		//WAITING_FOR_ENROLLMENT state
		student.setStatus(StudentStatus.WAITING_FOR_ENROLLMENT);
		new ExceptionExpectation() {
			public void execute() {
				studentService.approveRegistration(student);
			}
		}.checkForErrorCode(BusinessException.class, ErrorCodes.ERROR_STUDENT_SIGNED_UP_STATE);
		//REJECTED state
		student.setStatus(StudentStatus.REJECTED);
		new ExceptionExpectation() {
			public void execute() {
				studentService.approveRegistration(student);
			}
		}.checkForErrorCode(BusinessException.class, ErrorCodes.ERROR_STUDENT_SIGNED_UP_STATE);
	}
	
	/**
	 * User action:
	 * A signed student is being accepted by the admins.
	 * 
	 * Expected behavior:
	 * 1. The student should be available with the status {@link StudentStatus#WAITING_FOR_ENROLLMENT}
	 */
	@Specification(state = SpecificationState.COMPLETE)
	public void acceptStudentRegisration() {
		Student student = StudentTestFactory.createStudent();
		studentService.registerStudent(student);
		studentService.approveRegistration(student);
		expect.that(student.getStatus()).isEqualTo(StudentStatus.WAITING_FOR_ENROLLMENT);
		expect.that(studentService.listSignedUp(1, 2).getItems()).isEmpty();
		expect.that(studentService.listWaitingForEnrollment(1, 2).getItems()).isOfSize(1);
	}
	
	/**
	 * User action:
	 * A signed student with state other than {@link StudentStatus#SIGNED_UP} is
	 * denied by the admins.
	 * 
	 * Expected behavior:
	 * 1. Throw an {@link IllegalStateException}
	 */
	@Specification(state = SpecificationState.COMPLETE)
	public void failToRejectStudentIfNotInSignedUpState() {
		final Student student = StudentTestFactory.createStudent();
		
		student.setStatus(StudentStatus.WAITING_FOR_ENROLLMENT);
		new ExceptionExpectation() {			
			public void execute() {
				studentService.denyRegistration(student);
			}
		}.checkForErrorCode(BusinessException.class, ErrorCodes.ERROR_STUDENT_SIGNED_UP_STATE);
		
		student.setStatus(StudentStatus.ENROLLED);
		new ExceptionExpectation() {
			public void execute() {
				studentService.denyRegistration(student);
			}
		}.checkForErrorCode(BusinessException.class, ErrorCodes.ERROR_STUDENT_SIGNED_UP_STATE);
	}
	
	/**
	 * User action:
	 * A signed up student is being rejected by the admins.
	 * 
	 * Expected behavior:
	 * 1. The student should go to the rejected students pool.
	 */
	@Specification(state = SpecificationState.COMPLETE)
	public void rejectStudentRegistration() {
		Student student = StudentTestFactory.createStudent();
		studentService.registerStudent(student);
		studentService.denyRegistration(student);
		expect.that(studentService.listSignedUp(1, 2).getItems()).isEmpty();
		expect.that(studentService.listRejected(1, 2).getItems()).isOfSize(1);
	}
			
}